﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Mail;
using System.Net.Mime;
using System.Text;
using System.Text.RegularExpressions;
using DotNetNuke.Common;
using DotNetNuke.Entities.Portals;

namespace HeroldIT.Dnn.Modules.Services
{
    /// <summary>
    /// Provides advanced email services.
    /// </summary>
    public static class Mail
    {
        #region static fields

        private static readonly Regex htmlFinder = new Regex("<html.+?</html>", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline);

        private static readonly Regex imgFinder = new Regex("<img\\s+[^>]*src=([\"'])(?!https?://)(?<imageUrl>[^'\"]+)\\1", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase | RegexOptions.Singleline);

        #endregion

        #region static properties

        private static readonly object mimeTypesLock = new object();

        private static StringDictionary mimeTypesLookup;

        private static StringDictionary MimeTypesLookup
        {
            get
            {
                if (null == mimeTypesLookup)
                {
                    lock (mimeTypesLock)
                    {
                        if (null == mimeTypesLookup)
                        {
                            mimeTypesLookup = new StringDictionary();
                            mimeTypesLookup[".gif"] = MediaTypeNames.Image.Gif;
                            mimeTypesLookup[".jpg"] = MediaTypeNames.Image.Jpeg;
                            mimeTypesLookup[".jpeg"] = MediaTypeNames.Image.Jpeg;
                            mimeTypesLookup[".pdf"] = MediaTypeNames.Application.Pdf;
                        }
                    }
                }
                return mimeTypesLookup;
            }
        }

        #endregion

        #region static methods

        /// <summary>
        /// Gets an <see cref="SmtpClient"/> with the smtp settings configured in DNN's host settings.
        /// </summary>
        /// <returns>The <see cref="SmtpClient"/>.</returns>
        public static SmtpClient GetSmtpClient()
        {
            var smtpServer = Globals.HostSettings["SMTPServer"] as string;
            var smtpAuth = Globals.HostSettings["SMTPAuthentication"] as string;
            var smtpUser = Globals.HostSettings["SMTPUsername"] as string;
            var smtpPassword = Globals.HostSettings["SMTPPassword"] as string;

            if (String.IsNullOrEmpty(smtpServer))
                throw new InvalidOperationException("No smtp server specified in host settings.");

            int port = 25;
            int index = smtpServer.IndexOf(":");
            if (index >= 0)
            {
                port = int.Parse(smtpServer.Substring(index + 1), NumberStyles.None, CultureInfo.InvariantCulture);
                smtpServer = smtpServer.Substring(0, index);
            }
            SmtpClient client = new SmtpClient {Host = smtpServer, Port = port};

            if (!String.IsNullOrEmpty(smtpAuth) && !smtpAuth.Equals("0"))
            {
                if (smtpAuth.Equals("1"))
                {
                    if (String.IsNullOrEmpty(smtpUser))
                        throw new InvalidOperationException("No smtp user specified in host settings.");
                    if (String.IsNullOrEmpty(smtpUser))
                        throw new InvalidOperationException("No smtp password specified in host settings.");

                    client.UseDefaultCredentials = false;
                    client.Credentials = new NetworkCredential(smtpUser, smtpPassword);
                }
                else if (smtpAuth.Equals("2"))
                {
                    client.UseDefaultCredentials = true;
                }
            }
            return client;
        }

        /// <summary>
        /// Parses an html mail for images and add them as embedded resources.
        /// </summary>
        /// <param name="parseInto">The <see cref="MailMessage"/> to parse the html into.</param>
        /// <param name="htmlBody">The html code to use as the mail's body.</param>
        /// <param name="imageBaseDir">The base dir to included images, or <c>null</c> for the portal's home directory.</param>
        public static void ParseMail(this MailMessage parseInto, string htmlBody, string imageBaseDir)
        {
            if (null == parseInto)
                throw new ArgumentNullException("parseInto");
            if (String.IsNullOrEmpty(htmlBody))
                throw new ArgumentNullException("htmlBody");

            PortalSettings portalSettings = PortalController.GetCurrentPortalSettings();

            if (String.IsNullOrEmpty(imageBaseDir))
            {
                imageBaseDir = portalSettings.HomeDirectoryMapPath;
            }
            if (!Directory.Exists(imageBaseDir))
                throw new ArgumentException("Directory does not exists.", "imageBaseDir");

            if (!htmlFinder.IsMatch(htmlBody))
                throw new ArgumentException("The mail's html body is not html.", "htmlBody");

            List<string> imageIds = new List<string>();
            List<LinkedResource> resources = new List<LinkedResource>();
            string processedHtml =
                imgFinder.Replace(htmlBody,
                                  match =>
                                      {
                                          if (match.Success)
                                          {
                                              string imageUrl = match.Groups["imageUrl"].Value;
                                              string imageId =
                                                  Convert.ToBase64String(Encoding.UTF8.GetBytes(imageUrl));
                                              string imagePath = Path.Combine(imageBaseDir, imageUrl);
                                              if (!imageIds.Contains(imageId))
                                              {
                                                  if (File.Exists(imagePath))
                                                  {
                                                      imageIds.Add(imageId);
                                                      FileInfo imageInfo = new FileInfo(imagePath);
                                                      string contentType =
                                                          MimeTypesLookup.ContainsKey(imageInfo.Extension)
                                                              ? MimeTypesLookup[imageInfo.Extension]
                                                              : MediaTypeNames.Application.Octet;
                                                      resources.Add(new LinkedResource(imagePath)
                                                                        {
                                                                            ContentId = imageId,
                                                                            ContentType =
                                                                                new ContentType(
                                                                                contentType)
                                                                        });
                                                      return match.Value.Replace(imageUrl, "cid:" + imageId);
                                                  }
                                              }
                                              else
                                              {
                                                  return match.Value.Replace(imageUrl, "cid:" + imageId);
                                              }
                                          }
                                          return match.Value;
                                      });

            AlternateView htmlView = AlternateView.CreateAlternateViewFromString(processedHtml,
                                                                                 Encoding.Unicode,
                                                                                 MediaTypeNames.Text.Html);
            htmlView.TransferEncoding = TransferEncoding.QuotedPrintable;
            foreach (LinkedResource resource in resources)
            {
                htmlView.LinkedResources.Add(resource);
            }
            parseInto.AlternateViews.Add(htmlView);
        }

        /// <summary>
        /// Sends an html mail, embedding any images prior to sending.
        /// </summary>
        /// <param name="from">The sender's mail address, or <c>null</c> to use the portal's configured mail address.</param>
        /// <param name="to">Any regular recipient(s) for the mail.</param>
        /// <param name="bcc">Any bcc recipient(s) for the mail.</param>
        /// <param name="subject">The subject for the mail message.</param>
        /// <param name="textBody">The plaintext body of the mail message, or <c>null</c> for no plaintext part.</param>
        /// <param name="htmlBody">The html body of the mail message, or <c>null</c> for no html part.</param>
        /// <param name="imageBaseDir">The base directory to use for image paths, or <c>null</c> for the portal's home directory.</param>
        public static void SendMail(string from, string to, string bcc, string subject, string textBody, string htmlBody, string imageBaseDir)
        {
            if (String.IsNullOrEmpty(to) && String.IsNullOrEmpty(bcc))
                throw new ArgumentException("You have not specified any recipients.", "to");

            if (String.IsNullOrEmpty(htmlBody) && String.IsNullOrEmpty(textBody))
                throw new ArgumentException("You have not specified any mail body.", "htmlBody");

            if (String.IsNullOrEmpty(from))
            {
                PortalSettings portalSettings = PortalController.GetCurrentPortalSettings();
                from = portalSettings.Email;
            }
            using (var mail = new MailMessage
                                  {
                                      From = new MailAddress(from),
                                      Subject = subject,
                                      IsBodyHtml = true
                                  })
            {
                if (!String.IsNullOrEmpty(to))
                    mail.To.Add(to);
                if (!String.IsNullOrEmpty(bcc))
                    mail.Bcc.Add(bcc);

                mail.ParseMail(htmlBody, imageBaseDir);
                if (!String.IsNullOrEmpty(textBody))
                {
                    mail.Body = textBody;
                    mail.BodyEncoding = Encoding.GetEncoding("ISO-8859-1");
                    mail.IsBodyHtml = false;
                }

                SmtpClient smtpClient = GetSmtpClient();
                smtpClient.Send(mail);
            }
        }

        #endregion
    }
}
